package com.gvsoft;

import com.gvsoft.analyse.AnalyseTools;
import com.gvsoft.analyse.inf.IClientOrder;
import com.gvsoft.analyse.order.ClientKeepOrder;
import com.gvsoft.analyse.order.ClientMsgOrder;
import com.gvsoft.analyse.order.ClientOrder;
import com.gvsoft.busi.MsgInfo;
import com.gvsoft.busi.MsgQueueConsumer;
import com.gvsoft.common.CommonTools;
import com.gvsoft.thread.KeepChannelThread;
import com.gvsoft.thread.PacketConsumer;
import com.gvsoft.thread.ReconnectThread;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.*;

/**
 * Created with IntelliJ IDEA.
 * ProjectName:gvMsgClient
 * Author: zhaoqiubo
 * Date: 15/7/31
 * Time: 下午3:22
 * Desc: 客户端配置类，原来想用Config，等有时间再修改吧。现在有点乱。
 */
public class Client {

    private static String serverIp;

    private static int serverPort;

    private static String userId;

    private static Logger logger = LogManager.getLogger(Client.class.getName());

    /**
     * 报文区间分隔符，报文分为三个区间，HEADER区间、RID区间、BODY区间；
     * HEADER标记报文分类，RID为报文对象id，在一定周期内唯一；BODY为报文的内容区域。
     */
    public static String HBREGEX = "|";
    /**
     * 存储所有报文处理类的实例，一次性初始化进入Map（为连接通道（GVServer中使用）准备的单例类Map）
     */
    private static Map<String, Object> SINGLE_INSTANCE_PACKET_CLASS_MAP = new ConcurrentHashMap();
    /**
     * 存储所有报文处理类的实例，一次性初始化进入Map（为消息处理线程（CommunicateQueueConsumer中使用）准备的单例消息处理类Map）
     */
    private static Map<String, Object> SINGLE_INSTANCE_ORDER_CLASS_MAP = new ConcurrentHashMap();
    /**
     * 配置报文处理类分类的外部分隔符，分隔符两边代表某一类报文处理类的属性描述。
     * 例如：R:com.gvsoft.analyse.RegisterHandleModel|E:com.gvsoft.analyse.ErrorHandleModel
     */
    private final String SeparatorOuter = "|";
    /**
     * 配置报文处理类分类的内部部分隔符,分割某一类报文处理类的header和body。
     * 例如：R:com.gvsoft.analyse.RegisterHandleModel
     */
    private final String SeparatorInner = ":";
    /**
     * 标记客户端是否短线，用于短线重连、链路维护等线程
     */
    public static boolean IS_CONNECT = false;
    /**
     * 维持链路的周期
     */
    private static int keepAliveCycle = 5;
    /**
     * 重连的周期
     */
    private static int reconnectCycle = 10;

    /**
     * 链路维护超期次数：就是几次收不到回应启动重连，这个次数的标记。
     */
    private static int keepTimeoutCount = 3;

    /*------------------------------------------------------------------------------------
    【注意】keepTimeoutcount*keepAliveCycle 必须大于 reconnectCycle ，否则容易造成一直在重新连接
     ------------------------------------------------------------------------------------*/

    /**
     * 服务端报文队列，暂时放这里吧，等我迭代再说
     */
    public static BlockingQueue MSGQUEUE = new ArrayBlockingQueue<String>(1000);

    /**
     * 存储发送出去的维持链路报文
     */
    public static Map<String, ClientKeepOrder> KEEP_ORDER_MAP = new ConcurrentHashMap<String, ClientKeepOrder>();

    public String getServerIp() {
        return serverIp;
    }

    public void setServerIp(String serverIp) {
        this.serverIp = serverIp;
    }

    public int getServerPort() {
        return serverPort;
    }

    public void setServerPort(int serverPort) {
        this.serverPort = serverPort;
    }


    public static int getKeepAliveCycle() {
        return keepAliveCycle;
    }

    private void setKeepAliveCycle(int keepAliveCycle) {
        Client.keepAliveCycle = keepAliveCycle;
    }

    public static int getReconnectCycle() {
        return reconnectCycle;
    }

    private void setReconnectCycle(int reconnectCycle) {
        Client.reconnectCycle = reconnectCycle;
    }

    public static int getKeepTimeoutCount() {
        return keepTimeoutCount;
    }

    private void setKeepTimeoutCount(int keepTimeoutCount) {
        Client.keepTimeoutCount = keepTimeoutCount;
    }

    public static String getUserId() {
        return userId;
    }

    public static void setUserId(String userId) {
        Client.userId = userId;
    }

    public Client(boolean isInitCfg, String userId) {
        if (isInitCfg) {
            initConfig();
            this.setUserId(userId);
        }
    }

    private void initConfig() {

        //【客户端开发请注意】此处需要客户端自己添加读取配置信息，可以将配置文件中的ip和port
        // 读取进来，将程序中的clientid（有时指用户唯一标识）读取进来。
        Properties p = new Properties();
        try {
            InputStreamReader reader = new InputStreamReader(new FileInputStream
                    (System.getProperty("user.dir") + "/target/classes/cfg.properties"), "utf-8");
            p.load(reader);
            this.setServerIp(p.getProperty("server_ip").trim());
            this.setServerPort(Integer.parseInt(p.getProperty("server_port").trim()));
            this.setKeepAliveCycle(Integer.parseInt(p.getProperty("keep_alive_cycle").trim()));
            this.setKeepTimeoutCount(Integer.parseInt(p.getProperty("keep_timeout_count").trim()));
            this.setReconnectCycle(Integer.parseInt(p.getProperty("reconnect_cycle").trim()));

            initPacketMap(p.getProperty("packet_class"));
            initOrderMap(p.getProperty("order_class"));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 连接服务器，并发送登录指令
     * @param ip
     * @param port
     */
/*    public GVClientMThread connectServer(String ip,int port){
        GVClientMThread gvClient = new GVClientMThread();
        gvClient.connectServer(ip.trim(), port);
        return gvClient;
    }*/

    /**
     * 初始化报文实例池
     *
     * @param handleCfgStr
     */
    private void initPacketMap(String handleCfgStr) {
        String[] handleOuterArray = AnalyseTools.str2ArrayByChar(handleCfgStr, SeparatorOuter);
        try {
            for (int i = 0; i < handleOuterArray.length; i++) {
                String[] handleInnerArray = AnalyseTools.str2ArrayByChar(handleOuterArray[i], SeparatorInner);
                Class c = Class.forName(handleInnerArray[1]);
                Object handleClass = c.newInstance();
                SINGLE_INSTANCE_PACKET_CLASS_MAP.put(handleInnerArray[0], handleClass);
            }
            logger.info( "多路复用器所使用的报文处理单例类加载完毕…………");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化指令实例池
     *
     * @param handleCfgStr
     */
    private void initOrderMap(String handleCfgStr) {
        String[] handleOuterArray = AnalyseTools.str2ArrayByChar(handleCfgStr, SeparatorOuter);
        try {
            for (int i = 0; i < handleOuterArray.length; i++) {
                String[] handleInnerArray = AnalyseTools.str2ArrayByChar(handleOuterArray[i], SeparatorInner);
                Class c = Class.forName(handleInnerArray[1]);
                ClientOrder clientOrder = (ClientOrder) c.newInstance();
                clientOrder.setHeader(handleInnerArray[0]);
                SINGLE_INSTANCE_ORDER_CLASS_MAP.put(handleInnerArray[0], clientOrder);
            }
            logger.info("通讯队列消费者所使用指令处理单例类加载完毕…………");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 从报文实例池中取出操作实例
     *
     * @param classKey
     * @return
     */
    public static Object getPacketInstance(String classKey) {
        return Client.SINGLE_INSTANCE_PACKET_CLASS_MAP.get(classKey);
    }


    /**
     * 返回报文实例池Map
     *
     * @return
     */
    public static Map<String, Object> getPacketMap() {
        return Client.SINGLE_INSTANCE_PACKET_CLASS_MAP;
    }

    /**
     * 返回指令实例池Map
     *
     * @return
     */
    public static Map<String, Object> getOrderMap() {
        return Client.SINGLE_INSTANCE_ORDER_CLASS_MAP;
    }

    /**
     * 从指令实例池中取出操作实例
     *
     * @param classKey
     * @return
     */
    public static Object getOrderInstance(String classKey) {
        return Client.SINGLE_INSTANCE_ORDER_CLASS_MAP.get(classKey);
    }

    public static synchronized void write2Channel(IClientOrder order) throws IOException {
        GVClientMThread.write2Channel(order, GVClientMThread.socketChannel);

    }

    /**
     * 启动读取配置，连接服务器，以及各个线程。
     */
    public static void start(String userId) {
        Client client = new Client(true, userId);
        if (client.connectServer()) {
            ExecutorService threadPool = Executors.newCachedThreadPool();
            //启动多路复用器主线程
            threadPool.execute(new GVClientMThread());
            //启动报文消费者线程
            threadPool.execute(new PacketConsumer());

            threadPool.execute(new ReconnectThread());
            //启动链路维护线程
            threadPool.execute(new KeepChannelThread());
            threadPool.execute(new MsgQueueConsumer());


        }
    }

    public static boolean connectServer() {

        InetSocketAddress serverAddress = new InetSocketAddress(serverIp, serverPort);
        try {

            //打开socket通道
//            GVClientMThread.socketChannel = null;
            GVClientMThread.socketChannel = SocketChannel.open();
            //讲通道设置为非阻塞模式
            GVClientMThread.socketChannel.configureBlocking(false);
            //打开多路复用器
//            GVClientMThread.selector = null;
            GVClientMThread.selector = Selector.open();
            GVClientMThread.socketChannel.register(GVClientMThread.selector, SelectionKey.OP_CONNECT);
            //连接服务器，会触发服务端selector.select()，key的状态为OP_ACCEPT
            GVClientMThread.socketChannel.connect(serverAddress);
            logger.info("正在连接服务器中………………………………………………………………………………");
            return true;
        } catch (IOException e) {
            logger.info( "无法连接服务端，请检查服务端状态或客户端参数配置。");
            e.printStackTrace();
            return false;
        }

    }


    public static void clearToken4Disconnect() {
        Client.IS_CONNECT = false;
        //将所有Order单例类的token清空
        for (Map.Entry<String, Object> entry : Client.getOrderMap().entrySet()) {
            ((ClientOrder) entry.getValue()).setToken("");
        }
    }

    public synchronized static void sendMsg(MsgInfo msgInfo) {
        ClientMsgOrder msgOrder = (ClientMsgOrder) Client.getOrderInstance(ClientMsgOrder.HEADER);
        msgOrder.initClientMsgOrder(msgInfo);
        try {
            Client.write2Channel(msgOrder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String arg[]) {
//        YMP.initialize();\
//        System.out.println(System.getProperty("user.dir"));

        Client.start("13889298492");

//        (new Thread() {
//            public void run() {
//                try {
//                    int sleeptime = 200;
//                    logger.info("sleeptime:"+sleeptime);
//                    Thread.sleep(sleeptime);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                if (Client.IS_CONNECT) {
//                    for (int i = 0; i < 1000000; i++) {
//                        MsgInfo msgInfo = new MsgInfo("13889298480", "18604055343", CommonTools.systemTimeUtc(), "Estefdsadfasdf,fdasdfas<" + i + ">fdsadfas");
//
//                        Client.sendMsg(msgInfo);
//                        try {
//                            Thread.sleep(5);
//                        } catch (InterruptedException e) {
//                            e.printStackTrace();
//                        }
//                    }
//                }
//            }
//        }).start();

////        for(int i = 0;i<10;i++) {
//            MsgInfo msgInfo = new MsgInfo("13889298482", "13889298482", CommonTools.systemTimeUtc(), "测试消息发送");
//        MsgInfo msgInfo = new MsgInfo("13889298482", "13889298482", CommonTools.systemTimeUtc(), "测试消息发送");
//            Client.sendMsg(msgInfo);
//        }
        //Client.write2Channel();
//        thread.connectServer(thread.getServerIp(), thread.getServerPort(), thread.getClientId()).run();
    }

}
